#define __SHORT_CIRCUIT_ET__

// Need to short circuit macros for min/max/floor/ceil to enable ET
#include "polyTask.hpp"
#include "Wrapper.hpp"
#include "constants.h"

#include "./simple_expression_template/VAR.hpp"
#include "./simple_expression_template/TERM.hpp"
#include "./simple_expression_template/EXPRESSION.hpp"
#include "./simple_expression_template/ADD.hpp"
#include "./simple_expression_template/SUB.hpp"
#include "./simple_expression_template/MIN.hpp"
#include "./simple_expression_template/MAX.hpp"
#include "./simple_expression_template/CEIL.hpp"
#include "./simple_expression_template/FLOOR.hpp"
#include "./simple_expression_template/GE.hpp"
#include "./simple_expression_template/LE.hpp"
#include "./simple_expression_template/FOR.hpp"

#undef __SHORT_CIRCUIT_ET__

#include "TupleHelper.hpp"
// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int N, int K> struct computePredsHelper2 {
  template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
                            tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE)
  {
#if VERBOSE >= 10
    std::cerr << "At loop " << N << " cond is " << std::get<N>(condsE) << "\n";
#endif

    auto tmp = std::get<N>(tagAsTupleE);
    std::get<N>(tagAsTupleE) = tmp-1;  // DEP2
    std::get<N>(condsE) |= 
      !(std::get<INTERTASKDIM-K+1>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<INTERTASKDIM-K+1>(ubsE).Val(tagAsTupleE));
    assert(std::get<N>(tagAsTupleE) == tmp-1);  // DEP2
    std::get<N>(tagAsTupleE) = tmp;
    assert(std::get<N>(tagAsTupleE) == tmp);

#if VERBOSE >= 10
    std::cerr << "\t\tEvaluating expression for loop " << N << " at loop iteration: ";
    std::cerr << std::get<1>(tagAsTupleE) << " ";
    std::cerr << std::get<2>(tagAsTupleE) << " ";
    std::cerr << std::get<3>(tagAsTupleE) << " ";
    std::cerr << std::get<4>(tagAsTupleE) << " ";
    std::cerr << std::get<5>(tagAsTupleE) << " ";
    std::cerr << " gives " << std::get<N>(condsE) << "\n";
#endif

    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K-1>::compute
      (condsE, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int N> 
struct computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, 0> {
  template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
                            tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
};


// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int K, int N> struct computePredsHelper {
  template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
                            tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE)
  {
    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K>::compute
      (condsE, lbsE, ubsE, tagAsTupleE);

    computePredsHelper<TASKTAGDIM, INTERTASKDIM, K+1, N-1>::compute
      (condsE, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int K> 
struct computePredsHelper<TASKTAGDIM, INTERTASKDIM, K, 0> {
  template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
  static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
                            tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
};


template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM, int N>
struct CNCDeps {
  template<class ITEM_COLLECTION, class DC, typename...CONDSE>
  static void genDeps(ITEM_COLLECTION & ic,
                      DC &dc,
                      tuple<CONDSE...> &condsE, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {
    if (!std::get<N>(condsE)) {
      std::get<N-1>(tt.m_arr)-=1; // DEP2
      dc.depends(ic, TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt));
      tt.m_arr[N-1]+=1; // DEP2
    }
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genDeps(ic, dc, condsE, tt);
  }
  
  template<class ITEM_COLLECTION, typename...CONDSE>
  static void genGets(ITEM_COLLECTION & ic,
                      tuple<CONDSE...> &condsE, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
                      void *& wA) {
    if (!std::get<N>(condsE)) {
      std::get<N-1>(tt.m_arr)-=1; // DEP2
#if VERBOSE >= 5
      std::cerr << "Get " << N-1 << " tag: " << tt << "\n";
#endif

      ic.get(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt), wA);

#if VERBOSE >= 5
      std::cerr << "Success\n";
#endif
      tt.m_arr[N-1]+=1;  // DEP2
    }
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genGets(ic, condsE, tt, wA);
  }

  template<class ITEM_COLLECTION, typename...CONDSE>
  static void genZeroGet(ITEM_COLLECTION &ic,
                         tuple<CONDSE...> &condsE, 
                         void *& wA) {
    // If at least one of the conditions is true, this is not the first task
    // and we should get the task "zero" (zero atm, need to rename).
    if (!std::get<N>(condsE)) return;
    CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genZeroGet(ic, condsE, wA);
  }
};

template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
struct CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, 0> {
  template<class ITEM_COLLECTION, class DC, typename...CONDSE>
  static void genDeps(ITEM_COLLECTION &ic,
                      DC &dc,
                      tuple<CONDSE...> &condsE, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {}

  template<class ITEM_COLLECTION, typename...CONDSE>
  static void genGets(ITEM_COLLECTION &ic,
                      tuple<CONDSE...> &condsE, 
                      TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
                      void *& wA) {}

  template<class ITEM_COLLECTION, typename...CONDSE>
  static void genZeroGet(ITEM_COLLECTION &ic,
                          tuple<CONDSE...> &condsE, void *& wA) {
    // Leaf of the recursion, just get   
    TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> t
      (toArray(makeIdenticalTuple<int64_t, TASKTAGDIM>::make(ZERO_TASK_INDEX)));
#if VERBOSE >= 5
    std::cerr << "Get dummy " << t << "\n";
#endif

    ic.get(t, wA);

#if VERBOSE >= 5
    std::cerr << "Success\n";
#endif
  }
};














// #define __SHORT_CIRCUIT_ET__

// // Need to short circuit macros for min/max/floor/ceil to enable ET
// #include "polyTask.hpp"
// #include "Wrapper.hpp"
// #include "constants.h"

// #include "./simple_expression_template/VAR.hpp"
// #include "./simple_expression_template/TERM.hpp"
// #include "./simple_expression_template/EXPRESSION.hpp"
// #include "./simple_expression_template/ADD.hpp"
// #include "./simple_expression_template/SUB.hpp"
// #include "./simple_expression_template/MIN.hpp"
// #include "./simple_expression_template/MAX.hpp"
// #include "./simple_expression_template/CEIL.hpp"
// #include "./simple_expression_template/FLOOR.hpp"
// #include "./simple_expression_template/GE.hpp"
// #include "./simple_expression_template/LE.hpp"
// #include "./simple_expression_template/FOR.hpp"

// #undef __SHORT_CIRCUIT_ET__

// #include "TupleHelper.hpp"
// // Depth N-1 does   1 updates to std::get<N>
// // Depth N-2 does   2 updates to std::get<N-1>
// // ...
// // Depth N-k does N-k updates to std::get<N-k>
// // ...
// // Depth   1 does   N updates to std::get<1>
// // Depth   0 does   0 updates to std::get<1>
// // -> must start with N = INTERTASKDIM-1 and K=1
// template<int TASKTAGDIM, int INTERTASKDIM, int N, int K> struct computePredsHelper2 {
//   template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
//   static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
//                             tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE)
//   {
// #if VERBOSE >= 10
//     std::cerr << "At loop " << N << " cond is " << std::get<N>(condsE) << "\n";
// #endif

//     auto tmp = std::get<N>(tagAsTupleE);
//     std::get<N>(tagAsTupleE) = tmp-1;
//     std::get<N>(condsE) |= 
//       !(std::get<INTERTASKDIM-K+1>(lbsE).Val(tagAsTupleE)) ||
//       !(std::get<INTERTASKDIM-K+1>(ubsE).Val(tagAsTupleE));
//     assert(std::get<N>(tagAsTupleE) == tmp-1);
//     std::get<N>(tagAsTupleE) = tmp;
//     assert(std::get<N>(tagAsTupleE) == tmp);

// #if VERBOSE >= 10
//     std::cerr << "\t\tEvaluating expression for loop " << N << " at loop iteration: ";
//     std::cerr << std::get<1>(tagAsTupleE) << " ";
//     std::cerr << std::get<2>(tagAsTupleE) << " ";
//     std::cerr << std::get<3>(tagAsTupleE) << " ";
//     std::cerr << std::get<4>(tagAsTupleE) << " ";
//     std::cerr << std::get<5>(tagAsTupleE) << " ";
//     std::cerr << " gives " << std::get<N>(condsE) << "\n";
// #endif

//     computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K-1>::compute
//       (condsE, lbsE, ubsE, tagAsTupleE);
//   }
// };


// template<int TASKTAGDIM, int INTERTASKDIM, int N> 
// struct computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, 0> {
//   template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
//   static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
//                             tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
// };


// // Depth N-1 does   1 updates to std::get<N>
// // Depth N-2 does   2 updates to std::get<N-1>
// // ...
// // Depth N-k does N-k updates to std::get<N-k>
// // ...
// // Depth   1 does   N updates to std::get<1>
// // Depth   0 does   0 updates to std::get<1>
// // -> must start with N = INTERTASKDIM-1 and K=1
// template<int TASKTAGDIM, int INTERTASKDIM, int K, int N> struct computePredsHelper {
//   template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
//   static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
//                             tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE)
//   {
//     computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K>::compute
//       (condsE, lbsE, ubsE, tagAsTupleE);

//     computePredsHelper<TASKTAGDIM, INTERTASKDIM, K+1, N-1>::compute
//       (condsE, lbsE, ubsE, tagAsTupleE);
//   }
// };


// template<int TASKTAGDIM, int INTERTASKDIM, int K> 
// struct computePredsHelper<TASKTAGDIM, INTERTASKDIM, K, 0> {
//   template<typename...CONDSE, typename...LBSE, typename...UBSE, typename... INTS>
//   static void compute(tuple<CONDSE...> &condsE, tuple<LBSE...> const & lbsE, 
//                             tuple<UBSE...> const & ubsE, tuple<INTS...> & tagAsTupleE) {}
// };


// template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM, int N>
// struct CNCDeps {
//   template<class DC, typename...CONDSE>
//   static void genDeps(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                       DC &dc,
//                       tuple<CONDSE...> &condsE, 
//                       TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {
//     if (!std::get<N>(condsE)) {
//       std::get<N-1>(tt.m_arr)--;
//       dc.depends(c.M, TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt));
//       tt.m_arr[N-1]++;
//     }
//     CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genDeps(c, dc, condsE, tt);
//   }
  
//   template<typename...CONDSE>
//   static void genGets(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                       tuple<CONDSE...> &condsE, 
//                       TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
//                       void *& wA) {
//     if (!std::get<N>(condsE)) {
//       std::get<N-1>(tt.m_arr)--;
// #if VERBOSE >= 5
//       std::cerr << "Get " << N-1 << " tag: " << tt << "\n";
// #endif
//       c.M.get(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> (tt), wA);
// #if VERBOSE >= 5
//       std::cerr << "Success\n";
// #endif
//       tt.m_arr[N-1]++;
//     }
//     CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genGets(c, condsE, tt, wA);
//   }

//   template<typename...CONDSE>
//   static void genZeroGet(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                           tuple<CONDSE...> &condsE, 
//                           void *& wA) {
//     // If at least one of the conditions is true, this is not the first task
//     // and we should get the task "zero" (zero atm, need to rename).
//     if (!std::get<N>(condsE)) return;
//     CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, N-1>::genZeroGet(c, condsE, wA);
//   }
// };

// template<int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
// struct CNCDeps<TASK_ID, TASKTAGDIM, INTERTASKDIM, 0> {
//   template<class DC, typename...CONDSE>
//   static void genDeps(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                       DC &dc,
//                       tuple<CONDSE...> &condsE, 
//                       TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt) {}

//   template<typename...CONDSE>
//   static void genGets(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                       tuple<CONDSE...> &condsE, 
//                       TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &tt,
//                       void *& wA) {}

//   template<typename...CONDSE>
//   static void genZeroGet(polyTask_context<TASK_ID, TASKTAGDIM, INTERTASKDIM> & c,
//                           tuple<CONDSE...> &condsE, void *& wA) {
// #if VERBOSE >= 5
//     std::cerr << "Get dummy " << t << "\n";
// #endif

//     // Leaf of the recursion, just get   
//     c.M.get(TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> 
//             (toArray(makeIdenticalTuple<int64_t, TASKTAGDIM>::make(ZERO_TASK_INDEX))), wA);

// #if VERBOSE >= 5
//     std::cerr << "Success\n";
// #endif
//   }
// };
